﻿using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using DimensionsHelper.LanguageService.Symbols;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

/// <summary>
///     处理IBM DataCollection Script预处理语句的简单预处理器。
/// </summary>
/// <remarks>
///     <list type="bullet|number">
///         <item>
///             <term>#include</term> 引入指定路径的文件。
///         </item>
///         <item>
///             <term>#define|#undef</term> 添加宏定义并在调用出展开宏定义或移除宏定义。
///         </item>
///         <item>
///             <term>#line</term> 在读取时忽略，在输出结果时添加在 #include 展开的文件内容前。
///         </item>
///         <item>
///             <term>#error</term> 在使用此指令的位置报告错误。
///         </item>
///         <item>
///             <term>#if-#elif-#else-#endif</term> 进行条件展开，可以计算常量表达式。
///         </item>
///     </list>
/// </remarks>
public partial class Preprocessor
{
    private readonly DiagnosticStorage _diagnosticStorage;
    private readonly List<Directive> _directives = [];
    private readonly Lexer _lexer;
    private readonly ProcessedFile _processedFile;
    private readonly SourceTextFile _source;

    private PreNodeList? _currentBlock;
    private SourceNode _currentNode = SourceNode.Default;

    // Date, Time
    private string _date = "";
    private SourceNode _lastNode = SourceNode.Default;
    private string _time = "";

    private Preprocessor(SourceTextFile sourceFile)
    {
        _source = sourceFile;
        _processedFile = new ProcessedFile(_source);
        _diagnosticStorage = _processedFile.GetDiagnosticStorage();
        _lexer = new Lexer();
        _lexer.Initialize(sourceFile, _diagnosticStorage);
        _macroSymbolTable = _processedFile.MacroSymbols;
    }


    /// <summary>
    ///     如果需要抑制错误，将此属性设置为<see langword="true" />
    /// </summary>
    public bool SuppressDiagnostic { get; set; }


    /// <summary>
    ///     获取预处理器的错误信息列表。
    /// </summary>
    public IEnumerable<DiagnosticInfo> GetDiagnostics()
    {
        return _diagnosticStorage;
    }

    /// <summary>
    ///     获取预处理器中未使用的文本区间列表。此列表中的区间在条件预处理 (#if-#elif-#else-#endif)
    ///     中为通过常量表达式的真值检定。
    /// </summary>
    public IReadOnlyList<SyntaxLocation> GetUnusedLocations()
    {
        return _diagnosticStorage.GetUnusedSpans();
    }


    private void Raise(DiagnosticInfo info)
    {
        if (!SuppressDiagnostic)
        {
            _diagnosticStorage.Raise(info);
        }
    }


    /// <summary>
    ///     报告一个错误信息。
    /// </summary>
    /// <param name="diagnostic">预定义的错误信息模板</param>
    /// <param name="textNode">错误所在的节点位置</param>
    /// <param name="args">用于替换模板字符串中的文本</param>
    private void Raise(DiagnosticDescriptor diagnostic, ITextNode textNode, params object?[] args)
    {
        if (!SuppressDiagnostic)
        {
            _diagnosticStorage.Raise(diagnostic, textNode, args);
        }
    }


    /// <summary>
    ///     报告一个错误信息。
    /// </summary>
    /// <param name="diagnostic">预定义的错误信息模板</param>
    /// <param name="start">错误所在的起始位置</param>
    /// <param name="length">错误所在的文本宽度</param>
    /// <param name="args">用于替换模板字符串中的文本</param>
    private void Raise(DiagnosticDescriptor diagnostic, int start, int length, params object?[] args)
    {
        if (!SuppressDiagnostic)
        {
            _diagnosticStorage.Raise(diagnostic, SyntaxLocation.Create(_source.Uri, start, length), args);
        }
    }


    /// <summary>
    ///     报告一个错误信息。
    /// </summary>
    /// <param name="diagnostic">预定义的错误信息模板</param>
    /// <param name="span">错误所在的节点位置</param>
    /// <param name="args">用于替换模板字符串中的文本</param>
    private void Raise(DiagnosticDescriptor diagnostic, in TextSpan span,
        params object?[] args)
    {
        if (!SuppressDiagnostic)
        {
            _diagnosticStorage.Raise(diagnostic, span, args);
        }
    }


    /// <summary>
    ///     将指定文本位置设置为未使用的文本区间。
    /// </summary>
    /// <param name="span">指定文本区间</param>
    private void UnUse(in TextSpan span)
    {
        if (!SuppressDiagnostic)
        {
            _diagnosticStorage.UnUse(_source.Uri, span);
        }
    }


    private bool Expect(SyntaxKinds kind, bool allowPrecedingLinebreak = false)
    {
        bool noLfError = allowPrecedingLinebreak ||
                         !_currentNode.HasPrecedingLinebreak;

        if (_currentNode.IsNode(kind) && noLfError)
        {
            return true;
        }

        Raise(
            DiagnosticDescriptors.UnexpectedToken,
            !noLfError ? _lastNode.End : _currentNode.Start,
            !noLfError ? 1 : _currentNode.Width,
            SyntaxFacts.ConvertTokenToString(kind));

        return false;
    }


    private bool NextExpect(SyntaxKinds kind, bool allowPrecedingLinebreak,
        [MaybeNullWhen(false)] out SourceNode node)
    {
        node = null;

        if (!allowPrecedingLinebreak && IsNextWrap())
        {
            Raise(
                DiagnosticDescriptors.UnexpectedToken,
                _currentNode.End,
                1,
                SyntaxFacts.ConvertTokenToString(kind));

            return false;
        }

        node = ProcessNode();
        return Expect(kind);
    }


    private SourceNode ProcessNode()
    {
        _lastNode = _currentNode;
        _currentNode = _lexer.Lex();
        _currentNode.SetUri(_source.Uri);
        return _currentNode;
    }


    private bool IsNextWrap()
    {
        return _currentNode.HasTrailingLinebreak;
    }


    private void ExpectLinebreakAndSkip()
    {
        if (IsNextWrap())
        {
            return;
        }

        Raise(DiagnosticDescriptors.NeedLinebreak, _currentNode.End, 1);

        while (!IsNextWrap())
        {
            ProcessNode();
        }
    }


    private T FinishToken<T>(T token, string? tokenText = null)
        where T : AbstractTextNode
    {
        token.Update(
            _currentNode.Start,
            _currentNode.Width,
            string.IsNullOrEmpty(tokenText) ? _currentNode.Text : tokenText);
        token.UpdateLine(_currentNode.StartLine, _currentNode.EndLine);
        token.SetUri(_source.Uri);
        return token;
    }


    private T FinishToken<T>(T token, int start, int? length = null,
        string? text = null) where T : AbstractTextNode
    {
        int len = length ?? _currentNode.End - start;
        token.Update(start, len, text ?? _lexer.GetText(start, len));
        token.UpdateLine(_currentNode.StartLine, _currentNode.EndLine);
        token.SetUri(_source.Uri);
        return token;
    }


    private bool TryProcess(SyntaxKinds kind, [MaybeNullWhen(false)] out SourceNode node,
        bool allowNewline = false)
    {
        if (!_lexer.TryLex(kind, out node, allowNewline))
        {
            return false;
        }

        _lastNode = _currentNode;
        _currentNode = node;
        node.SetUri(_source.Uri);
        return true;
    }


    private bool TryProcess(Predicate<SourceNode> predicate, [MaybeNullWhen(false)] out SourceNode node,
        bool allowNewline = false)
    {
        SourceNode? sourceNode = null;
        _lexer.TryLex(() =>
        {
            sourceNode = _lexer.Lex();
            if (!predicate(sourceNode) || (!allowNewline && sourceNode.HasPrecedingLinebreak))
            {
                sourceNode = null;
                return false;
            }

            _lastNode = _currentNode;
            _currentNode = sourceNode;
            sourceNode.SetUri(_source.Uri);
            return true;
        });

        node = sourceNode;
        return node != null;
    }


    private bool Process(params SyntaxKinds[] stopTokens)
    {
        SourceNode token = ProcessNode();

        if (token.GetFlags(SyntaxNodeFlags.StartOfFile) && !_lexer.GetComments().IsEmpty)
        {
            ProcessCommentDirective();
        }

        if (Array.IndexOf(stopTokens, token.Kind) != -1)
        {
            return false;
        }

        switch (token.Kind)
        {
            // macro
            case SyntaxKinds.PreDefineKeywordToken:
                ProcessMacro(token);
                break;

            // #if
            case SyntaxKinds.PreIfKeywordToken:
                ProcessIfStatement(token, null);
                break;

            // #ifdef
            case SyntaxKinds.PreIfDefKeywordToken:
            // #ifndef
            case SyntaxKinds.PreIfNDefKeywordToken:
                ProcessIfDefOrNDefStatement(token);
                break;

            // #error
            case SyntaxKinds.PreErrorKeywordToken:
                ProcessErrorDirective(token);
                break;

            // #undef
            case SyntaxKinds.PreUndefKeywordToken:
                ProcessUndefDirective();
                break;

            // #line
            case SyntaxKinds.PreLineKeywordToken:
                ProcessLineDirective();
                break;

            // #include
            case SyntaxKinds.PreIncludeKeywordToken:
                ProcessInclude(token);
                break;

            case SyntaxKinds.Identifier:
                string idText = _currentNode.Text;

                // 优先扩展预定义宏
                SourceNode? processedId = idText switch
                {
                    "__FILE__" => NodeFactory.CreateSourceString(null, token.Start, token.Width, _source.FileName),
                    "__DATE__" => NodeFactory.CreateSourceString(null, token.Start, token.Width, _date),
                    "__TIME__" => NodeFactory.CreateSourceString(null, token.Start, token.Width, _time),
                    "__LINE__" => NodeFactory.CreateSourceString(null, token.Start, token.Width,
                        token.StartLine.ToString()),
                    _ => null
                };

                SourceIdentifier sourceId = NodeFactory.CreateSourceIdentifier(_currentNode);

                if (processedId != null)
                {
                    MacroExpansion expandId = NodeFactory.CreateMacroExpansion(null, sourceId);
                    expandId.UpdateFrom(sourceId);
                    expandId.AddNode(processedId);
                    _processedFile.AddNode(expandId);
                    break;
                }

                // 检查是否是宏定义
                if (_macroSymbolTable.TryGetSymbol(idText, out ISymbol? symbol) &&
                    symbol.IsDeclared(_source.FileName))
                {
                    // 区分参数宏和非参数宏
                    // 调用参数宏时，左括号前可以有空格、换行
                    TryProcess(SyntaxKinds.LeftParenthesisToken, out SourceNode? lp, true);
                    // 参数宏
                    MacroSymbol macroSymbol = (MacroSymbol)symbol;
                    Macro macro = macroSymbol.GetLastDeclaration();
                    if (macro.IsParametered && lp != null)
                    {
                        MacroCallExpression callExpr = ProcessMacroCallExpression(sourceId, lp);
                        MacroExpansion paramExp = ExpandParameterMacro(callExpr, macroSymbol);
                        NormalizeToFile(FlattenExpansion(paramExp));
                    }
                    else if (!macro.IsParametered && lp == null)
                    {
                        MacroExpansion nonParamExpansion = ExpandNormalMacro(sourceId, macroSymbol);
                        NormalizeToFile(FlattenExpansion(nonParamExpansion));
                    }
                    // 否则，当作一般标识符处理
                    else
                    {
                        _processedFile.AddNode(sourceId);
                    }
                }
                else
                {
                    _processedFile.AddNode(sourceId);
                }

                break;

            // 对于单个 # ，如果前后都换行（包含注释），直接跳过
            case SyntaxKinds.HashToken:
                if (!token.HasTrailingLinebreak || !token.HasPrecedingLinebreak)
                {
                    Raise(DiagnosticDescriptors.InvalidToken, token);
                }

                break;

            default:
                _processedFile.AddNode(token);

                if (token.IsNode(SyntaxKinds.Unknown))
                {
                    Raise(DiagnosticDescriptors.InvalidToken, token);
                }

                break;
        }

        return true;
    }


    private void FinishProcessing()
    {
        _processedFile.EndOfFileToken = _currentNode;
        _processedFile.SetComments(_lexer.GetComments());
        // 加入末尾注释
        _processedFile.AddTrailingComments(_lexer.GetCurrentComments());
        _processedFile.TimeStamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
    }


    /// <summary>
    ///     读取文件头部的注释指令信息。
    /// </summary>
    /// <param name="source">源文件</param>
    public static IReadOnlyList<Directive> ReadCommentDirectives(SourceTextFile source)
    {
        Preprocessor processor = new(source)
        {
            _date = DateTime.Now.Date.ToString(CultureInfo.InvariantCulture),
            _time = DateTime.Now.TimeOfDay.ToString(),
            SuppressDiagnostic = true
        };

        processor.ProcessNode();
        processor.ProcessCommentDirective();
        return processor._directives;
    }


    private ProcessedFile ProcessSourceFile(SourceTextFile? source = null)
    {
        source ??= _source;
        if (source.Preprocessed != null)
        {
            return source.Preprocessed;
        }

        bool startOfFile = true;
        while (Process(SyntaxKinds.Eof))
        {
            if (!startOfFile)
            {
                continue;
            }

            // 加入开头的注释列表
            _processedFile.AddLeadingComments(_lexer.GetComments());
            startOfFile = false;
        }

        FinishProcessing();
        return _processedFile;
    }


    /// <summary>
    ///     对指定的源文件进行预处理，并返回展开后的预处理文件。
    /// </summary>
    /// <param name="sourceFile">原始文件对象</param>
    /// <param name="definedSymbols">需要引用的符号表</param>
    /// <returns>预处理后的文件对象</returns>
    public static ProcessedFile Process(SourceTextFile sourceFile, ReadOnlySymbolTable? definedSymbols = null)
    {
        if (sourceFile.Preprocessed != null)
        {
            return sourceFile.Preprocessed;
        }

        Preprocessor processor = new(sourceFile)
        {
            _date = DateTime.Now.Date.ToString(CultureInfo.InvariantCulture),
            _time = DateTime.Now.TimeOfDay.ToString()
        };

        if (definedSymbols != null)
        {
            processor._macroSymbolTable.Join(definedSymbols);
        }

        return processor.ProcessSourceFile();
    }
}